///////////////////////////////////////////////////////////////////////////////
//
// wxFormBuilder - A Visual Dialog Editor for wxWidgets.
// Copyright (C) 2005 José Antonio Hurtado
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// Written by
//   José Antonio Hurtado - joseantonio.hurtado@gmail.com
//   Juan Antonio Ortega  - jortegalalmolda@gmail.com
//
///////////////////////////////////////////////////////////////////////////////

#include "objecttree.h"

#include "../../model/objectbase.h"
#include "../appdata.h"
#include "../bitmaps.h"
#include "../menueditor.h"
#include "../wxfbevent.h"

#include <wx/imaglist.h>

BEGIN_EVENT_TABLE( ObjectTree, wxPanel )
	EVT_TREE_SEL_CHANGED(wxID_ANY, ObjectTree::OnSelChanged)
	EVT_TREE_ITEM_RIGHT_CLICK(wxID_ANY, ObjectTree::OnRightClick)
	EVT_TREE_BEGIN_DRAG(wxID_ANY, ObjectTree::OnBeginDrag)
	EVT_TREE_END_DRAG(wxID_ANY, ObjectTree::OnEndDrag)
	EVT_TREE_KEY_DOWN(wxID_ANY, ObjectTree::OnKeyDown)

	EVT_FB_PROJECT_LOADED( ObjectTree::OnProjectLoaded )
	EVT_FB_PROJECT_SAVED( ObjectTree::OnProjectSaved )
	EVT_FB_OBJECT_CREATED( ObjectTree::OnObjectCreated )
	EVT_FB_OBJECT_REMOVED( ObjectTree::OnObjectRemoved )
	EVT_FB_PROPERTY_MODIFIED( ObjectTree::OnPropertyModified )
	EVT_FB_PROJECT_REFRESH( ObjectTree::OnProjectRefresh )

END_EVENT_TABLE()

ObjectTree::ObjectTree( wxWindow *parent, int id )
:
wxPanel( parent, id )
{
	AppData()->AddHandler( this->GetEventHandler() );
	m_tcObjects = new wxTreeCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_HAS_BUTTONS|wxTR_LINES_AT_ROOT|wxTR_DEFAULT_STYLE|wxSIMPLE_BORDER);

	wxBoxSizer* sizer_1 = new wxBoxSizer(wxVERTICAL);
    sizer_1->Add(m_tcObjects, 1, wxEXPAND, 0);
    SetAutoLayout(true);
    SetSizer(sizer_1);
    sizer_1->Fit(this);
    sizer_1->SetSizeHints(this);
    Connect( wxID_ANY, wxEVT_FB_OBJECT_EXPANDED, wxFBObjectEventHandler( ObjectTree::OnObjectExpanded ) );
    Connect( wxID_ANY, wxEVT_FB_OBJECT_SELECTED, wxFBObjectEventHandler( ObjectTree::OnObjectSelected ) );
    Connect( wxID_ANY, wxEVT_COMMAND_TREE_ITEM_EXPANDED, wxTreeEventHandler( ObjectTree::OnExpansionChange ) );
    Connect( wxID_ANY, wxEVT_COMMAND_TREE_ITEM_COLLAPSED, wxTreeEventHandler( ObjectTree::OnExpansionChange ) );

    m_altKeyIsDown = false;
}

void ObjectTree::OnKeyDown(wxTreeEvent &event )
{
	if( event.GetKeyEvent().AltDown() && event.GetKeyCode() != WXK_ALT)
	{
#ifdef __WXGTK__
		switch (event.GetKeyCode())
		{
			case WXK_UP:
				AppData()->MovePosition(GetObjectFromTreeItem(m_tcObjects->GetSelection()),false);
				return;
			case WXK_DOWN:
				AppData()->MovePosition(GetObjectFromTreeItem(m_tcObjects->GetSelection()),true);
				return;
			case WXK_RIGHT:
				AppData()->MoveHierarchy(GetObjectFromTreeItem(m_tcObjects->GetSelection()),false);
				return;
			case WXK_LEFT:
				AppData()->MoveHierarchy(GetObjectFromTreeItem(m_tcObjects->GetSelection()),true);
				return;
		}
#endif
		event.Skip();
	}
	else
	{
		event.Skip();
	}
}


ObjectTree::~ObjectTree()
{
	AppData()->RemoveHandler( this->GetEventHandler() );
}

PObjectBase ObjectTree::GetObjectFromTreeItem( wxTreeItemId item )
{
	if ( item.IsOk() )
	{
		wxTreeItemData* item_data = m_tcObjects->GetItemData( item );
		if ( item_data )
		{
			PObjectBase obj( ((ObjectTreeItemData* )item_data)->GetObject() );
			return obj;
		}
	}
	return PObjectBase( (ObjectBase*)NULL );
}

void ObjectTree::RebuildTree()
{
	m_tcObjects->Freeze();

	Disconnect( wxID_ANY, wxEVT_COMMAND_TREE_ITEM_EXPANDED, wxTreeEventHandler( ObjectTree::OnExpansionChange ) );
	Disconnect( wxID_ANY, wxEVT_COMMAND_TREE_ITEM_COLLAPSED, wxTreeEventHandler( ObjectTree::OnExpansionChange ) );

	PObjectBase project = AppData()->GetProjectData();

	// Clear the old tree and map
	m_tcObjects->DeleteAllItems();
	m_map.clear();

	if (project)
	{
		wxTreeItemId dummy;
		AddChildren(project, dummy, true );

		// Expand items that were previously expanded
		RestoreItemStatus(project);
	}

	Connect( wxID_ANY, wxEVT_COMMAND_TREE_ITEM_COLLAPSED, wxTreeEventHandler( ObjectTree::OnExpansionChange ) );
	Connect( wxID_ANY, wxEVT_COMMAND_TREE_ITEM_EXPANDED, wxTreeEventHandler( ObjectTree::OnExpansionChange ) );

	m_tcObjects->Thaw();
}

void ObjectTree::OnSelChanged(wxTreeEvent &event)
{
	wxTreeItemId id = event.GetItem();
	if (!id.IsOk()) return;

	// Make selected items bold
	wxTreeItemId oldId = event.GetOldItem();
	if ( oldId.IsOk() )
	{
		m_tcObjects->SetItemBold( oldId, false );
	}
	m_tcObjects->SetItemBold( id );

	wxTreeItemData *item_data = m_tcObjects->GetItemData(id);

	if (item_data)
	{
		PObjectBase obj(((ObjectTreeItemData *)item_data)->GetObject());
		assert(obj);
		Disconnect( wxID_ANY, wxEVT_FB_OBJECT_SELECTED, wxFBObjectEventHandler( ObjectTree::OnObjectSelected ) );
		AppData()->SelectObject(obj);
		Connect( wxID_ANY, wxEVT_FB_OBJECT_SELECTED, wxFBObjectEventHandler( ObjectTree::OnObjectSelected ) );
	}
}

void ObjectTree::OnRightClick(wxTreeEvent &event)
{
	wxTreeItemId id = event.GetItem();
	wxTreeItemData *item_data = m_tcObjects->GetItemData(id);
	if (item_data)
	{
		PObjectBase obj(((ObjectTreeItemData *)item_data)->GetObject());
		assert(obj);
		wxMenu * menu = new ItemPopupMenu(obj);
		wxPoint pos = event.GetPoint();
		menu->UpdateUI(menu);
		PopupMenu(menu,pos.x, pos.y);
	}
}

void ObjectTree::OnBeginDrag(wxTreeEvent &event)
{
	// need to explicitly allow drag
    if ( event.GetItem() == m_tcObjects->GetRootItem() )
    {
    	return;
    }

	m_draggedItem = event.GetItem();
	event.Allow();
}

void ObjectTree::OnEndDrag(wxTreeEvent& event)
{
	bool copy = ::wxGetKeyState( WXK_CONTROL );

    wxTreeItemId itemSrc = m_draggedItem,
                 itemDst = event.GetItem();
    m_draggedItem = (wxTreeItemId)0l;

	// ensure that itemDst is not itemSrc or a child of itemSrc
	wxTreeItemId item = itemDst;
	while ( item.IsOk() )
	{
		if ( item == itemSrc )
		{
			return;
		}
		item = m_tcObjects->GetItemParent( item );
	}

	PObjectBase objSrc = GetObjectFromTreeItem( itemSrc );
	if ( !objSrc )
	{
		return;
	}

	PObjectBase objDst = GetObjectFromTreeItem( itemDst );
	if ( !objDst )
	{
		return;
	}

	// backup clipboard
	PObjectBase clipboard = AppData()->GetClipboardObject();

	// set object to clipboard
	if ( copy )
	{
		AppData()->CopyObject( objSrc );
	}
	else
	{
		AppData()->CutObject( objSrc );
	}

	if ( !AppData()->PasteObject( objDst ) && !copy )
	{
		AppData()->Undo();
	}

	AppData()->SetClipboardObject( clipboard );
}

void ObjectTree::OnExpansionChange(wxTreeEvent &event)
{
	wxTreeItemId id = event.GetItem();
	wxTreeItemData *item_data = m_tcObjects->GetItemData(id);
	if (item_data)
	{
		PObjectBase obj(((ObjectTreeItemData *)item_data)->GetObject());
		assert(obj);

		Disconnect( wxID_ANY, wxEVT_FB_OBJECT_EXPANDED, wxFBObjectEventHandler( ObjectTree::OnObjectExpanded ) );
		Disconnect( wxID_ANY, wxEVT_COMMAND_TREE_ITEM_EXPANDED, wxTreeEventHandler( ObjectTree::OnExpansionChange ) );

		AppData()->ExpandObject( obj, m_tcObjects->IsExpanded( id ) );

		Connect( wxID_ANY, wxEVT_FB_OBJECT_EXPANDED, wxFBObjectEventHandler( ObjectTree::OnObjectExpanded ) );
		Connect( wxID_ANY, wxEVT_COMMAND_TREE_ITEM_EXPANDED, wxTreeEventHandler( ObjectTree::OnExpansionChange ) );
	}
}

void ObjectTree::AddChildren(PObjectBase obj, wxTreeItemId &parent, bool is_root)
{
	if (obj->GetObjectInfo()->GetObjectType()->IsItem())
	{
		if (obj->GetChildCount() > 0)
			AddChildren(obj->GetChild(0),parent);
		else
		{
			// Si hemos llegado aquí ha sido porque el arbol no está bien formado
			// y habrá que revisar cómo se ha creado.
			wxString msg;
			PObjectBase itemParent = obj->GetParent();
			assert(parent);

			msg = wxString::Format(wxT("Item without object as child of \'%s:%s\'"),
				itemParent->GetPropertyAsString(wxT("name")).c_str(),
				itemParent->GetClassName().c_str());

			wxLogError(msg);
		}
	}
	else
	{
		wxTreeItemId new_parent;

		ObjectTreeItemData *item_data = new ObjectTreeItemData(obj);

		if (is_root)
			new_parent = m_tcObjects->AddRoot(wxT(""),-1,-1,item_data);
		else
		{
			unsigned int pos = 0;

			PObjectBase parent_obj = obj->GetParent();
			// find a proper position where the added object should be displayed at
			if( parent_obj->GetObjectInfo()->GetObjectType()->IsItem() )
			{
				parent_obj = parent_obj->GetParent();
				pos = parent_obj->GetChildPosition( obj->GetParent() );
			}
			else
				pos = parent_obj->GetChildPosition( obj );

			// insert tree item to proper position
			if( pos > 0 )
				new_parent = m_tcObjects->InsertItem(parent, pos, wxT(""), -1, -1, item_data);
			else
				new_parent = m_tcObjects->AppendItem(parent,wxT(""),-1,-1,item_data);
		}

		// Add the item to the map
		m_map.insert( ObjectItemMap::value_type( obj, new_parent ) );

		// Set the image
		int image_idx = GetImageIndex( obj->GetObjectInfo()->GetClassName() );

		m_tcObjects->SetItemImage(new_parent,image_idx);

		// Set the name
		UpdateItem( new_parent, obj );

		// Add the rest of the children
		unsigned int count = obj->GetChildCount();
		unsigned int i;
		for (i = 0; i < count ; i++)
		{
			PObjectBase child = obj->GetChild(i);
			AddChildren(child, new_parent);
		}
	}
}


int ObjectTree::GetImageIndex (wxString name)
{
	int index = 0; //default icon

	IconIndexMap::iterator it = m_iconIdx.find(name);
	if (it != m_iconIdx.end())
		index = it->second;

	return index;
}

void ObjectTree::UpdateItem(wxTreeItemId id, PObjectBase obj)
{
	// mostramos el nombre
	wxString class_name( obj->GetClassName() );
	PProperty prop = obj->GetProperty( wxT("name") );
	wxString obj_name;
	if (prop)
	{
		obj_name =prop->GetValue();
	}

	wxString text = obj_name + wxT(" : ") + class_name;

	// actualizamos el item
	m_tcObjects->SetItemText(id,text);
}

void ObjectTree::Create()
{
	// Cramos la lista de iconos obteniendo los iconos de los paquetes.
	unsigned int index = 0;
	m_iconList = new wxImageList(ICON_SIZE,ICON_SIZE);

	{
		wxBitmap icon = AppBitmaps::GetBitmap(wxT("project"), ICON_SIZE);
		m_iconList->Add(icon);
		m_iconIdx.insert(IconIndexMap::value_type( wxT("_default_"),index++));
	}

	unsigned int pkg_count = AppData()->GetPackageCount();
	for (unsigned int i = 0; i< pkg_count;i++)
	{
		PObjectPackage pkg = AppData()->GetPackage(i);

		unsigned int j;
		for (j=0;j<pkg->GetObjectCount();j++)
		{
			wxString comp_name(pkg->GetObjectInfo(j)->GetClassName());
			m_iconList->Add( pkg->GetObjectInfo(j)->GetIconFile() );
			m_iconIdx.insert(IconIndexMap::value_type(comp_name,index++));
		}
	}

	m_tcObjects->AssignImageList(m_iconList);
}

void ObjectTree::RestoreItemStatus(PObjectBase obj)
{
	ObjectItemMap::iterator item_it = m_map.find(obj);
	if (item_it != m_map.end())
	{
		wxTreeItemId id = item_it->second;

		if ( obj->GetExpanded() )
			m_tcObjects->Expand(id);
		/*else
			m_tcObjects->Collapse(id);*/
	}

	unsigned int i,count = obj->GetChildCount();
	for (i = 0; i<count ; i++)
		RestoreItemStatus(obj->GetChild(i));
}

void ObjectTree::AddItem(PObjectBase item, PObjectBase parent)
{
	if( item && parent )
	{
		// find parent item displayed in the object tree
		while( parent && parent->GetObjectInfo()->GetObjectType()->IsItem() ) parent = parent->GetParent();

		// add new item to the object tree
		ObjectItemMap::iterator it = m_map.find( parent );
		if( (it != m_map.end()) && it->second.IsOk() )
		{
			AddChildren( item, it->second, false );
		}
	}
}

void ObjectTree::RemoveItem(PObjectBase item)
{
	// remove affected object tree items only
	ObjectItemMap::iterator it = m_map.find( item );
	if( (it != m_map.end()) && it->second.IsOk() )
	{
		m_tcObjects->Delete( it->second );
		// clear map records for all item's children
		ClearMap( it->first );
	}
}

void ObjectTree::ClearMap(PObjectBase obj)
{
	m_map.erase( obj );

	for ( unsigned int i = 0; i < obj->GetChildCount(); i++ )
	{
		ClearMap( obj->GetChild( i ) );
	}
}

/////////////////////////////////////////////////////////////////////////////
// wxFormBuilder Event Handlers
/////////////////////////////////////////////////////////////////////////////
void ObjectTree::OnProjectLoaded( wxFBEvent &)
{
	RebuildTree();
}

void ObjectTree::OnProjectSaved( wxFBEvent &)
{
}

void ObjectTree::OnObjectExpanded( wxFBObjectEvent& event )
{
	PObjectBase obj = event.GetFBObject();
	ObjectItemMap::iterator it = m_map.find( obj );
	if ( it != m_map.end() )
	{
		if ( m_tcObjects->IsExpanded( it->second ) != obj->GetExpanded() )
		{
			if ( obj->GetExpanded() )
			{
				m_tcObjects->Expand( it->second );
			}
			else
			{
				m_tcObjects->Collapse( it->second );
			}
		}
	}
}

void ObjectTree::OnObjectSelected( wxFBObjectEvent &event )
{
    PObjectBase obj = event.GetFBObject();

	// Find the tree item associated with the object and select it
	ObjectItemMap::iterator it = m_map.find(obj);
	if ( it != m_map.end() )
	{
		// Ignore expand/collapse events
		Disconnect( wxID_ANY, wxEVT_COMMAND_TREE_ITEM_EXPANDED, wxTreeEventHandler( ObjectTree::OnExpansionChange ) );
		Disconnect( wxID_ANY, wxEVT_COMMAND_TREE_ITEM_COLLAPSED, wxTreeEventHandler( ObjectTree::OnExpansionChange ) );

		m_tcObjects->EnsureVisible( it->second );
		m_tcObjects->SelectItem( it->second );

		// Restore event handling
		Connect( wxID_ANY, wxEVT_COMMAND_TREE_ITEM_EXPANDED, wxTreeEventHandler( ObjectTree::OnExpansionChange ) );
		Connect( wxID_ANY, wxEVT_COMMAND_TREE_ITEM_COLLAPSED, wxTreeEventHandler( ObjectTree::OnExpansionChange ) );
	}
	else
	{
		wxLogError( wxT("There is no tree item associated with this object.\n\tClass: %s\n\tName: %s"), obj->GetClassName().c_str(), obj->GetPropertyAsString(wxT("name")).c_str() );
	}
}

void ObjectTree::OnObjectCreated ( wxFBObjectEvent &event )
{
	//RebuildTree();

	if( event.GetFBObject() ) AddItem( event.GetFBObject(), event.GetFBObject()->GetParent() );
}

void ObjectTree::OnObjectRemoved ( wxFBObjectEvent &event )
{
	//RebuildTree();

	RemoveItem( event.GetFBObject() );
}

void ObjectTree::OnPropertyModified ( wxFBPropertyEvent &event )
{
  PProperty prop = event.GetFBProperty();

	if (prop->GetName() == wxT("name") )
	{
		ObjectItemMap::iterator it = m_map.find(prop->GetObject());
		if (it != m_map.end())
		{
			UpdateItem(it->second,it->first);
		}
	}
}

void ObjectTree::OnProjectRefresh ( wxFBEvent &)
{
	RebuildTree();
}

///////////////////////////////////////////////////////////////////////////////

ObjectTreeItemData::ObjectTreeItemData(PObjectBase obj) : m_object(obj)
{}

///////////////////////////////////////////////////////////////////////////////

enum {
	MENU_MOVE_UP = wxID_HIGHEST + 2000,
	MENU_MOVE_DOWN,
	MENU_MOVE_RIGHT,
	MENU_MOVE_LEFT,
	MENU_CUT,
	MENU_PASTE,
	MENU_EDIT_MENUS,
	MENU_COPY,
	MENU_MOVE_NEW_BOXSIZER,
	MENU_DELETE,
};


BEGIN_EVENT_TABLE(ItemPopupMenu,wxMenu)
	EVT_MENU(wxID_ANY, ItemPopupMenu::OnMenuEvent)
	EVT_UPDATE_UI(wxID_ANY, ItemPopupMenu::OnUpdateEvent)
END_EVENT_TABLE()

ItemPopupMenu::ItemPopupMenu(PObjectBase obj) : m_object(obj) {
	Append(MENU_CUT,        wxT("Cut\tCtrl+X"));
	Append(MENU_COPY,       wxT("Copy\tCtrl+C"));
	Append(MENU_PASTE,      wxT("Paste\tCtrl+V"));
	AppendSeparator();
	Append(MENU_DELETE,     wxT("Delete\tCtrl+D"));
	AppendSeparator();
	Append(MENU_MOVE_UP,    wxT("Move Up\tAlt+Up"));
	Append(MENU_MOVE_DOWN,  wxT("Move Down\tAlt+Down"));
	Append(MENU_MOVE_LEFT,  wxT("Move Left\tAlt+Left"));
	Append(MENU_MOVE_RIGHT, wxT("Move Right\tAlt+Right"));
	AppendSeparator();
	Append(MENU_MOVE_NEW_BOXSIZER, wxT("Move into a new wxBoxSizer"));
	AppendSeparator();
	Append(MENU_EDIT_MENUS, wxT("Menu Editor..."));
}

void ItemPopupMenu::OnMenuEvent (wxCommandEvent & event)
{
	int id = event.GetId();

	switch (id)
	{
	case MENU_CUT:
		AppData()->CutObject(m_object);
		break;
	case MENU_COPY:
		AppData()->CopyObject(m_object);
		break;
	case MENU_PASTE:
		AppData()->PasteObject(m_object);
		break;
	case MENU_DELETE:
		AppData()->RemoveObject(m_object);
		break;
	case MENU_MOVE_UP:
		AppData()->MovePosition(m_object,false);
		break;
	case MENU_MOVE_DOWN:
		AppData()->MovePosition(m_object,true);
		break;
	case MENU_MOVE_RIGHT:
		AppData()->MoveHierarchy(m_object,false);
		break;
	case MENU_MOVE_LEFT:
		AppData()->MoveHierarchy(m_object,true);
		break;
	case MENU_MOVE_NEW_BOXSIZER:
		AppData()->CreateBoxSizerWithObject(m_object);
		break;
	case MENU_EDIT_MENUS:
		{
			PObjectBase obj = m_object;
			if (obj && (obj->GetClassName() == wxT("wxMenuBar") || obj->GetClassName() == wxT("Frame") ) )
			{
				MenuEditor me(NULL);
				if (obj->GetClassName() == wxT("Frame") )
				{
					bool found = false;
					PObjectBase menubar;
					for (unsigned int i = 0; i < obj->GetChildCount() && !found; i++)
					{
						menubar = obj->GetChild(i);
						found = menubar->GetClassName() == wxT("wxMenuBar");
					}
					if (found) obj = menubar;
				}

				if (obj->GetClassName() == wxT("wxMenuBar")) me.Populate(obj);
				if (me.ShowModal() == wxID_OK)
				{
					if (obj->GetClassName() == wxT("wxMenuBar"))
					{
						PObjectBase menubar = me.GetMenubar(AppData()->GetObjectDatabase());
						while (obj->GetChildCount() > 0)
						{
							PObjectBase child = obj->GetChild(0);
							obj->RemoveChild(0);
							child->SetParent(PObjectBase());
						}
						for (unsigned int i = 0; i < menubar->GetChildCount(); i++)
						{
							PObjectBase child = menubar->GetChild(i);
							AppData()->InsertObject(child,obj);
						}
					}
					else
						AppData()->InsertObject(me.GetMenubar(AppData()->GetObjectDatabase()),AppData()->GetSelectedForm());
				}
			}
		}
		break;
	default:
		break;
	}
}

void ItemPopupMenu::OnUpdateEvent(wxUpdateUIEvent& e)
{
	switch (e.GetId())
	{
	case MENU_EDIT_MENUS:
		e.Enable(m_object && (m_object->GetClassName() == wxT("wxMenuBar" )
			|| m_object->GetClassName() == wxT("Frame")));
		break;
	case MENU_CUT:
	case MENU_COPY:
	case MENU_DELETE:
	case MENU_MOVE_UP:
	case MENU_MOVE_DOWN:
	case MENU_MOVE_LEFT:
	case MENU_MOVE_RIGHT:
	case MENU_MOVE_NEW_BOXSIZER:
		e.Enable(AppData()->CanCopyObject());
		break;
	case MENU_PASTE:
		e.Enable(AppData()->CanPasteObject());
		break;
	}
}


